﻿using System;
using System.Collections.Generic;
using System.Linq;
using Test30.Core;
using Test30.Engine;

namespace Test30.Tools.DataView
{
    public class MapHandler
    {
        public Array2D<Tile> Map{ get; private set; }
        public int MapWidth { get; set; }
        public int MapHeight { get; set; }
        public int PercentAreWalls { get; set; }
        public int Floors { get; private set; }
        public int Walls { get; private set; }

        
        private Tile Floor => new Tile(TileType.Floor);
        private Tile Wall => new Tile(TileType.Wall);

        public MapHandler()
        {
            MapWidth = 28;
            MapHeight = 16;
            PercentAreWalls = 45;

            RandomFillMap();
        }

        public MapHandler(int mapWidth, int mapHeight, Array2D<Tile> map, int percentWalls = 40)
        {
            MapWidth = mapWidth;
            MapHeight = mapHeight;
            PercentAreWalls = percentWalls;
            Map = map;
        }

        public void MakeCaverns()
        {
            Array2D<Tile> temp = new Array2D<Tile>(MapWidth,MapHeight);
            for (int i = 0; i < 3; i++)
            {
                foreach (Vec pos in Map.Bounds)
                {
                    temp[pos] = PlaceWallLogic(pos);
                }
                foreach (Vec pos in temp.Bounds)
                {
                    Map[pos] = temp[pos];
                }
            }
            CheckMap();
        }

        public void RandomFillMap()
        {
            // New, empty map
            Map = new Array2D<Tile>(MapWidth, MapHeight);

            for (int row = 0; row < MapHeight; row++)
            {
                for (int column = 0; column < MapWidth; column++)
                {
                    // If coordinants lie on the the edge of the map (creates a border)
                    if (column == 0)
                    {
                        Map[column, row] = Wall;
                    }
                    else if (row == 0)
                    {
                        Map[column, row] = Wall;
                    }
                    else if (column == MapWidth - 1)
                    {
                        Map[column, row] = Wall;
                    }
                    else if (row == MapHeight - 1)
                    {
                        Map[column, row] = Wall;
                    }
                    // Else, fill with a wall a random percent of the time
                    else
                    {
                        Map[column, row] = RandomPercent(PercentAreWalls);
                    }
                }
            }
        }

        public void GeneratorCount()
        {
            int f = 0, w = 0;
            foreach (Vec pos in Map.Bounds)
            {
                if (Map[pos].Type == TileType.Floor)
                    f++;
                else
                    w++;
            }
            Floors = f;
            Walls = w;
        }

        public void MakeStairs()
        {
            if (Floors < 100) return;
            Vec tl, tr, bl, br;
            tl = tr = bl = br = Vec.Zero;
            for (int i = 1; i < MapHeight; i++)
            {
                if (tl == Vec.Zero)
                {
                    StairRange(0, 0, i, ref tl);
                }
                if (tr == Vec.Zero)
                {
                    StairRange(MapWidth - i, 0, i, ref tr);
                }
                if (bl == Vec.Zero)
                {
                    StairRange(0, MapHeight - i, i, ref bl);
                }
                if (br == Vec.Zero)
                {
                    StairRange(MapWidth - i , MapHeight - i, i, ref br);
                }
                if (tl != Vec.Zero &&
                    tr != Vec.Zero &&
                    bl != Vec.Zero &&
                    br != Vec.Zero) break;
            }

            if (tl.Distance(br) > tr.Distance(bl))
            {
                Map[tl].Type = TileType.StairsDown;
                Map[br].Type = TileType.StairsUp;
            }
            else
            {
                Map[tr].Type = TileType.StairsDown;
                Map[bl].Type = TileType.StairsUp;
            }
        }

        void StairRange(int x, int y, int r, ref Vec i)
        {
            var rect = new Rect(x, y, r, r);
            foreach (Vec pos in rect)
            {
                if (Map[pos].Type == TileType.Floor)
                {
                    i = pos;
                    return;
                }
            }
        }

        void CheckMap()
        {
            var floors = new List<Vec>();
            foreach (Vec pos in Map.Bounds)
            {
                if (Map[pos].Type == TileType.Floor) floors.Add(pos);
            }
            if (floors.Count < 1) throw new ArgumentNullException();


            var step = new List<Vec>();
            List<List<Vec>> groups = new List<List<Vec>>();

            var temp = new List<Vec>();

            

            bool b = IsDirty(floors[0].X + 1, floors[0].Y);

            while (floors.Count>0)
            {
                //初始条件
                temp.Clear();
                step.Add(floors[0]);
                Map[floors[0]].Dirty = true;
                //建立step
                foreach (Vec pos in step)
                {
                    var neighbors = HasNeighbor(pos);
                    if (neighbors.Count > 0)
                    {
                        neighbors.ForEach(o => Map[o].Dirty = true);
                        temp.AddRange(neighbors);
                    }
                }
                step = temp.ToList();
                if (step.Count == 0)
                {
                    List<Vec> group = floors.Where(o => Map[o].Dirty).ToList();
                    if (group.Count / (float) Map.Bounds.Size.Area > 0.35f)
                    {
                        SetMap(group);
                        return;
                    }
                    groups.Add(group);
                    group.ForEach(o => floors.Remove(o));
                }
            }
            if (groups.Count > 0)
            {
                foreach (List<Vec> list in groups)
                {
                    if (list.Count / (float)Map.Bounds.Size.Area > 0.4f)
                    {
                        SetMap(list);
                        return;
                    }
                }
                MakeCaverns();
            }
        }

        void SetMap(List<Vec> floors)
        {
            Map.Fill(Wall);
            floors.ForEach(o => Map[o] = Floor);
        }

        List<Vec> HasNeighbor(Vec pos)
        {
            List<Vec> temp = new List<Vec>();
            for (int y = pos.Y - 1; y <= pos.Y+1; y++)
            {
                for (int x = pos.X - 1; x <= pos.X+1; x++)
                {
                    if (!(x == pos.X && y == pos.Y))
                    {
                        if (IsWall(x, y) || IsDirty(x, y))
                            continue;
                        temp.Add(new Vec(x, y));
                    }
                }
            }
            return temp;
        }

        Tile PlaceWallLogic(Vec pos)
        {
            //方案A
            /*
            int count = GetAdjacentWalls(pos, 1, 1);
            Tile temp;
            if (Map[pos].Type == TileType.Wall)
                temp = (count >= 4) ? Wall :Floor;
            else
                temp = (count >= 5) ? Wall : Floor;
            return temp;
             */
             //方案B
            int f = GetAdjacentWalls(pos, 1, 1);
            int g = GetAdjacentWalls(pos, 2, 2);

            if (f >= 5 || g <= 2)
                return Wall;
            return Floor;
        }

        int GetAdjacentWalls(Vec pos, int scopeX, int scopeY)
        {
            int startX = pos.X - scopeX;
            int startY = pos.Y - scopeY;
            int endX = pos.X + scopeX;
            int endY = pos.Y + scopeY;

            int iY;

            int wallCounter = 0;

            for (iY = startY; iY <= endY; iY++)
            {
                int iX;
                for (iX = startX; iX <= endX; iX++)
                {
                    if (!(iX == pos.X && iY == pos.Y))
                    {
                        if (IsWall(iX, iY))
                        {
                            wallCounter += 1;
                        }
                    }
                }
            }
            return wallCounter;
        }

        bool IsDirty(int x, int y)
        {
            if (IsOutOfBounds(x, y))return true;

            if (Map[x, y].Dirty)return true;

            return false;
        }

        bool IsWall(int x, int y)
        {
            // Consider out-of-bound a wall
            if (IsOutOfBounds(x, y))return true;

            if (Map[x, y].Type == TileType.Wall)return true;

            if (Map[x, y].Type == TileType.Floor)return false;

            return false;
        }

        bool IsOutOfBounds(int x, int y)
        {
            if (x < 0 || y < 0)
            {
                return true;
            }
            else if (x > MapWidth - 1 || y > MapHeight - 1)
            {
                return true;
            }
            return false;
        }

        Tile RandomPercent(int percent)
        {
            if (percent >= Rng.Int(1, 101))
            {
                return Wall;
            }
            return Floor;
        }
    }
}
